package org.ddevil.data;

import java.math.BigInteger;
import java.util.Comparator;

/**
 * This class will compare strings taking numbers and letters into account. 
 * 
 * <table width = 100% border=1>
 * <tr>
 * <td colspan="3" align="center"><strong>History</strong></td>
 * </tr>
 * <tr>
 * <th>Date</th>
 * <th>Name</th>
 * <th>Desc</th>
 * </tr>
 * <tr>
 * <td>2/26/07</td>
 * <td>Rich O'Connell</td>
 * <td>Added a singleton instance for a sick performance enhancement.</td>
 * </tr>
 * 
 * </table>
 * 
 * @author Eric Lundin
 * 
 */
public class StringNaturalOrderComparator implements Comparator {

	private static int MAX_INT_STRING_LENGTH = String
			.valueOf(Integer.MAX_VALUE).length();

	/**
	 * Create a comparator which will compare strings taking numbers and letters
	 * into account.
	 */
	public StringNaturalOrderComparator() {
		super();
	}
    
    private static StringNaturalOrderComparator singleton;
    
    /**
     * Get the singleton instance for this comparator.
     * @return
     */
    public static StringNaturalOrderComparator instance(){
        if(singleton == null)
            singleton = new StringNaturalOrderComparator();
        return singleton;
    }

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.Comparator#compare(T, T)
	 */
	public int compare(Object o1, Object o2) {
		String s1 = (String) o1;
		String s2 = (String) o2;
		// The original string we are comparing
		String originalString1 = s1.toUpperCase();
		String originalString2 = s2.toUpperCase();

		// The current characters we are comparing
		Character currentCharacter1;
		Character currentCharacter2;

		// The max loop size we can make.
		int shortStringLength;

		// Used to store number values for comparison.
		String currentNumberString1;
		String currentNumberString2;
		// Used when another comparator is called and we must analyze the
		// result.
		int compValue;
		int newI = 0;

		shortStringLength = Math.min(originalString1.length(), originalString2
				.length());
		for (int i = 0; i < shortStringLength; i++) {

			currentCharacter1 = originalString1.charAt(i);
			currentCharacter2 = originalString2.charAt(i);
			if (isNumeric(currentCharacter1) && isNumeric(currentCharacter2)) {
				// Start the string with the first number.
				currentNumberString1 = currentCharacter1.toString();
				currentNumberString2 = currentCharacter2.toString();

				// While there are more numbers keep building strings
				for (int j = i + 1; j < originalString1.length(); j++) {
					String n1 = ((Character) originalString1.charAt(j))
							.toString();
					newI = j - 1;
					if (isNumeric(n1)) {
						currentNumberString1 += n1;
					} else {
						break;
					}
				}
				for (int j = i + 1; j < originalString2.length(); j++) {
					String n2 = ((Character) originalString2.charAt(j))
							.toString();
					newI = Math.max(newI, j - 1);
					if (isNumeric(n2)) {
						currentNumberString2 += n2;
					} else {
						break;
					}
				}

				if (newI > i)
					i = newI;

				if (currentNumberString1.length() >= MAX_INT_STRING_LENGTH
						|| currentNumberString2.length() >= MAX_INT_STRING_LENGTH) {
					if ((compValue = new BigInteger(currentNumberString1)
							.compareTo(new BigInteger(currentNumberString2))) == 0)
						continue;
					else {
						return compValue;

					}
				} else {
					if ((compValue = new Integer(currentNumberString1)
							.compareTo(new Integer(currentNumberString2))) == 0)
						continue;
					else {
						return compValue;
					}
				}
			}

			else if (!currentCharacter1.equals(currentCharacter2)) {
				boolean c1IsNumeric = isNumeric(currentCharacter1);
				boolean c2IsNumeric = isNumeric(currentCharacter2);

				if (c1IsNumeric && !c2IsNumeric)
					return -1;
				if (!c1IsNumeric && c2IsNumeric)
					return 1;
				if (!c1IsNumeric && !c2IsNumeric) {
					if (currentCharacter1 < currentCharacter2)
						return -1;
					if (currentCharacter1 > currentCharacter2)
						return 1;
				}
			}
		}

		// Based on the characters compared there has been no difference. Try
		// to
		// find a difference in lengths or return 0 showing these two values
		// are
		// equal.
		if (originalString1.length() < originalString2.length())
			return -1;
		else if (originalString2.length() < originalString1.length())
			return 1;
		else
			return 0;
	}

	public static boolean isNumeric(Character x) {
		if (x.charValue() <= '9' && x.charValue() >= '0')
			return true;
		else
			return false;
	}

	public static boolean isNumeric(String x) {
		for (int i = 0; i < x.length(); i++)
			if (!isNumeric(x.charAt(i)))
				return false;
		return true;
	}

	public static boolean isAlpha(Character x) {
		return !isNumeric(x);
	}

	public static boolean isAlpha(String x) {
		return !isNumeric(x);
	}

}
